# 手写相关

# 判断千分位

var a = 222122122.6754;
var b = a.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, '$1,');//使用正则替换,每隔三个数加一个','
1
2
function format(num){  
  num=num+'';//数字转字符串  
  var str="";//字符串累加  
  for(var i=num.length-1,j=1;i>=0;i--,j++){  
      if(j%3==0 && i!=0){//每隔三位加逗号,过滤正好在第一个数字的情况  
          str+=num[i]+",";//加千分位逗号  
          continue;  
      }  
      str+=num[i];//倒着累加数字
  }  
  return str.split('').reverse().join("");//字符串=>数组=>反转=>字符串  
} 
1
2
3
4
5
6
7
8
9
10
11
12

# 洗牌算法(有序数组乱序)

// arr 是一个有序数组
function (arr) {
  for (i = arr.length - 1; i > 0; i--) {  
	//随机数生成器,范围[0, i]  
	int rand = parsetInt(Math.random()*i) + 1;  
		
	let temp = arr[i];  
	arr[i] = arr[rand];  
	arr[rand] = temp;  
  }
}
1
2
3
4
5
6
7
8
9
10
11

# 快速排序

function quickSortOne(arr, left, right) {
  if (left > right) {
  	return
  }
  let temp = arr[left]
  let i = left, j = right
  while (i !== j) {
  	while (arr[j] >= temp && i < j) { // 顺序很重要,要先从右往左找
      j--
    }
    while (arr[i] <= temp && i < j) { // 再从左往右找
      i++
    }
    if (i < j) { // 说明两者还未相遇,交换位置
      let t = arr[i]
      arr[i] = arr[j]
      arr[j] = t
    }
  }
  arr[left] = arr[i]
  arr[i] = temp
  quickSortOne(arr, left, i-1) // 继续处理左边的
  quickSortOne(arr, i+1, right) // 继续处理右边的
}

// 对arr[l...r]范围内的数组进行插入排序
function insertSort(arr, l, r){
  for (let i = l+1; i <= r; i++) {
      let e = arr[i]
      let j
      for (j = i; j > l && arr[j-1] > e; j--) {
          arr[j] = arr[j-1]
      }
      arr[j] = e
  }
}

// 对arr[l...r]部分进行partition操作
// 返回p,使得arr[l...p-1] < arr[p]; arr[p+1...r] > arr[p]
function __partition(arr, l, r) {

  // 待优化:数据量大,可以取 a[l]到a[r]之间的随机数
  // swap(arr[l],arr[random()%(r-l+1)+l])
  // let tempOne = arr[l]
  // arr[l] = arr[rand()%(r-l+1)+l]
  // arr[rand()%(r-l+1)+l] = tempOne

  let v = arr[l]
  // arr[l+1...j] < v; arr[j+1...i] > v
  let j = l
  for (let i = l + 1; i <= r; i++) {
      if (arr[i] < v) {
          // swap(arr[j+1], arr[i])
          let temp = arr[j+1]
          arr[j+1] = arr[i]
          arr[i] = temp
          j++
      }
  }
  // swap(arr[l],arr[j])
  let tempTwo = arr[l]
  arr[l] = arr[j]
  arr[j] = tempTwo
  return j
}

// 对 arr[l...r]部分进行快速排序
function __quickSort(arr, l, r) {
  if (l >= r) {
      return
  }
  // if (r - l <= 15) { // 优化,当数组范围小于15位采用插入排序
  // 	insertSort(arr, l, r)
  // 	return;
  // }

  let p = __partition(arr, l , r)
  __quickSort(arr, l , p - 1)
  __quickSort(arr, p + 1, r)
}

function quickSort(arr, n) {
  __quickSort(arr, 0, n - 1)
}


// 三路快速排序处理 arr[l...r]
// arr[l...r] 分为 <v ; ==v ; >v 三部分
// 之后递归对 <v ; >v 两部分进行三路快速排序
function __quickSort3Ways(arr, l, r) {
  if (l >= r) {
    	return;
  }
  // if (r - l <= 15) { // 优化,当数组范围小于15位采用插入排序
  //     insertSort(arr, l, r);
  //     return;
  // }

  // partition
  // 优化取 a[l]到a[r]之间的随机数
  // swap(arr[l],arr[random()%(r-l+1)+l]);
  // let tempOne = arr[l];
  // arr[l] = arr[rand()%(r-l+1)+l];
  // arr[rand()%(r-l+1)+l] = tempOne;

  let v = arr[l];

  let lt = l; // arr[l+1...lt] < v
  let gt = r + 1; // arr[gt...r] > v
  let i = l + 1; // arr[lt+1...i) == v
  while(i < gt) {
    if (arr[i] < v) {
        // swap(arr[i], arr[lt + 1]);
        let tempTwo = arr[i];
        arr[i] = arr[lt + 1];
        arr[lt + 1] = tempTwo;
        lt++;
        i++;
    } else if (arr[i] > v) {
        // swap(arr[i], arr[gt - 1]);
        let tempThree = arr[i];
        arr[i] = arr[gt - 1];
        arr[gt - 1] = tempThree;
        gt--;
    } else {
        i++;
    }
  }
  // swap(arr[l],arr[lt]);
  let tempFour = arr[l];
  arr[l] = arr[lt];
  arr[lt] = tempFour;

  __quickSort3Ways(arr, l, lt - 1);
  __quickSort3Ways(arr, gt, r);
}

function quickSort3Ways(arr, n) {
  __quickSort3Ways(arr, 0, n - 1);
}


let testArr = [5,1,6,4,3,8,2,7,10,9]
quickSortOne(testArr, 0, 9)
console.log(testArr)

let arr = [50,30,10,40,60,80,70,100,20,90]
quickSort(arr, 10)  // 快速排序调用
console.log(arr)

let testArr2 = [5,1,6,4,3,8,2,7,10,9,18,15,12,16]
quickSort3Ways(testArr2, 14)
console.log(testArr2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

# 归并排序

// 自顶向下的归并排序
function mergeSort(arr, n) {
  __mergeSort(arr, 0, n - 1)
}
// 递归使用归并排序,对arr[l...r]的范围进行排序
function __mergeSort(arr, l, r) {
  if (l >= r) {
  	return
  }
  // if (r - l <= 15) { // 优化,当数组范围小于15位采用插入排序
  //     insertSort(arr, l, r);
  //     return;
  // }
  let mid = l + Math.floor((r - l) / 2) // 防止越界
  __mergeSort(arr, l, mid)
  __mergeSort(arr, mid + 1, r)
  if (arr[mid] > arr[mid + 1]) { // 还未有序则需要归并排序
  	__merge(arr, l, mid, r)
  }
}
// 将arr[l...mid]和arr[mid+1...r]两部分进行归并
function __merge(arr, l, mid, r) {
  // 拷贝一个新数组用于归并操作
  let auxArr = []
  for (let i = l; i <= r; i++) {
    auxArr[i-l] = arr[i]
  }
  // 归并, 利用 auxArr 值进行判断,对原数组 arr 进行赋值操作
  let n = l, m = mid + 1
  for (let k = l; k <= r; k++) {
  	if (n > mid) { // 如果 n 越界
      arr[k] = auxArr[m - l]
      m++
    } else if (m > r) { // 如果 m 越界
      arr[k] = auxArr[n - l]
      n++
    } else if (auxArr[n - l] < auxArr[m - l]) { // 没有越界的情况下判断
      arr[k] = auxArr[n - l]
      n++
    } else {
      arr[k] = auxArr[m - l]
      m++
    }
  }
}
// 对arr[l...r]范围内的数组进行插入排序
function insertSort (arr, l, r){
  for (let i = l+1; i <= r; i++) {
      let e = arr[i]
      let j
      for (j = i; j > l && arr[j-1] > e; j--) {
        arr[j] = arr[j-1]
      }
      arr[j] = e
  }
}

function mergeSortBU (arr, n) { // 自底向上的归并(还未优化)
  for (let sz = 1; sz <= n; sz += sz) { // 第一次看一个元素,第二次看两个元素,第三次看4个元素。。。每次加上自己
    for (let i = 0; i + sz < n; i += sz + sz) { // 对 [0 ~ sz-1] [sz ~ 2sz-1] [2sz ~ 3sz-1]。。。进行归并,i + sz < n 防止越界
      // 对 arr[i...i+sz-1] 和 arr[i+sz+sz-1] 进行归并
      let r = Math.min(i + sz + sz - 1, n - 1) // min取最小值防止越界
      if (arr[i + sz - 1] > arr[i + sz]) { // 还未有序则需要归并排序
	    __merge(arr, i, i + sz - 1, r);
      }
    }
  }
}

let testArr = [5,1,6,4,3,8,2,7,10,9]
mergeSort(testArr, 10);
console.log(testArr)
let testArr2 = [50,10,60,40,30,80,20,70,100,90]
mergeSortBU(testArr2, 10)
console.log(testArr2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

# 数组展平

let arr = [[1,2],[[3],4]]

方法一 简单循环递归
function flatten(arr) {
  var result = [];
  for (var i = 0, len = arr.length; i < len; i++) {
    if (Array.isArray(arr[i])) {
      result = result.concat(flatten(arr[i]))
    }
    else {
      result.push(arr[i])
    }
  }
  return result;
}

// 方法二 map 递归
function flatten(arr) {
return [].concat(
  ...arr.map(x =>
    Array.isArray(x) ? flatten(x):x
  )
  )
}
console.log(flatten(arr))// ->[1,2,3,4]

// 方法二 flat api
function flattenDeep(arr, deepLength) {
  return arr.flat(deepLength)
}

// 方法三 利用 reduce
function flattenDeep(arr) {
  return arr.reduct((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), [])
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 对象数组去重

function flat (array) {
	let stringArray = array.map(item => {
		return JSON.stringify(item)
	})
	return [...new Set(stringArray)].map(item => {
		return JSON.parse(item)
	})
}
1
2
3
4
5
6
7
8

# 遍历一颗DOM树

unction traversal(node) {
  //对node的处理
  if (node && node.nodeType === 1) {
    console.log(node.tagName);
  }
  var i = 0,
    childNodes = node.childNodes,
    item;
  for (; i < childNodes.length; i++) {
    item = childNodes[i];
    if (item.nodeType === 1) {
      //递归先序遍历子节点
      traversal(item);
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 使用 reduce 实现数组 map 方法

//使用 reduce 实现数组 map 方法
const selfMap2 = function (fn, context){
    let arr = Array.prototype.slice.call(this)
    // 这种实现方法和循环的实现方法有异曲同工之妙,利用reduce contact起数组中每一项
    // 不过这种有个弊端,会跳过稀疏数组中为空的项
    return arr.reduce((pre, cur, index) => {
        return [...pre, fn.call(context, cur, index, this)]
    }, [])
}

Array.prototype.selfMap = selfMap2
var arr1 = [1, 2, 3]
arr1.length = 5

let arrMap = arr1.selfMap(function (x) {
    return x * 2
})
// [2, 4, 6]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 实现继承

// 寄生组合继承
function Parent(value) {
  this.val = value
}
Parent.prototype.getValue = function() {
  console.log(this.val)
}

function Child(value) {
  Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
  constructor: {
    value: Child,
    enumerable: false,
    writable: true,
    configurable: true
  }
})

const child = new Child(1)

child.getValue() // 1
child instanceof Parent // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// class 继承
class Parent {
  constructor(value) {
    this.val = value
  }
  getValue() {
    console.log(this.val)
  }
}
class Child extends Parent {
  constructor(value) {
    super(value)
    this.val = value
  }
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 实现一个JSON.stringify

function jsonStringify(obj) {
  let type = typeof obj;
  if (type !== "object") {
    if (/string|undefined|function/.test(type)) {
      obj = '"' + obj + '"';
    }
    return String(obj);
  } else {
    let json = []
    let arr = Array.isArray(obj)
    for (let k in obj) {
      let v = obj[k];
      let type = typeof v;
      if (/string|undefined|function/.test(type)) {
        v = '"' + v + '"';
      } else if (type === "object") {
        v = jsonStringify(v);
      }
      json.push((arr ? "" : '"' + k + '":') + String(v));
    }
    return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
  }
}
jsonStringify({x : 5}) // "{"x":5}"
jsonStringify([1, "false", false]) // "[1,"false",false]"
jsonStringify({b: undefined}) // "{"b":"undefined"}"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 实现JSON.parse

var json = '{"name":"cxk", "age":25}';
var obj = eval("(" + json + ")");
1
2

# 解析 URL Params 为对象

function parseParam(url) {
  const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
  const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
  let paramsObj = {};
  // 将 params 存到对象中
  paramsArr.forEach(param => {
    if (/=/.test(param)) { // 处理有 value 的参数
      let [key, val] = param.split('='); // 分割 key 和 value
      val = decodeURIComponent(val); // 解码
      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字

      if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
        paramsObj[key] = [].concat(paramsObj[key], val);
      } else { // 如果对象没有这个 key,创建 key 并设置值
        paramsObj[key] = val;
      }
    } else { // 处理没有 value 的参数
      paramsObj[param] = true;
    }
  })

  return paramsObj;
}

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 结果
{ user: 'anonymous',
  id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
  city: '北京', // 中文需解码
  enabled: true, // 未指定值得 key 约定为 true
}
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 模板引擎实现

function render(template, data) {
  const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
  if (reg.test(template)) { // 判断模板里是否有模板字符串
    const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
    return render(template, data); // 递归的渲染并返回渲染后的结构
  }
  return template; // 如果模板没有模板字符串直接返回
}

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
  name: '姓名',
  age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 动手实现一个 repeat 方法

function repeat(func, times, wait) {
  times--
  console.log(times)
  let that = this
  let timer = setTimeout(_ => {
  	func.apply(that)
  	clearTimeout(timer)
  	if (times > 0) {
  		repeat(func, times, wait)
  	}
  }, wait)
}
// 调用这个 repeatFunc ("hellworld"),会 alert 4 次 helloworld, 每次间隔3秒
const repeatFunc = repeat(alert, 4, 3000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 函数柯里化

通用方法:
function curry(fn, ...args) {
    var length = fn.length;
    var args = args || [];
    return function(){
        newArgs = args.concat(Array.prototype.slice.call(arguments));
        if (newArgs.length < length) {
            return curry.call(this,fn,...newArgs);
        }else{
            return fn.apply(this,newArgs);
        }
    }
}

function multiFn(a, b, c) {
    return a * b * c;
}

var multi = curry(multiFn);

multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// es6
const curry = (fn, ...args) => {
  args.length < fn.length
    // 参数长度不足时,重新柯里化该函数,等待接收新参数
	? (...arguments) => curry(fn, ...args, ...arguments)
	// 参数长度满足时,执行函数
	: fn(...args)
}
let curryTest=curry((a,b,c,d)=>a+b+c+d)
curryTest(1,2,3)(4) //返回10
curryTest(1,2)(4)(3) //返回10
curryTest(1,2)(3,4) //返回10
1
2
3
4
5
6
7
8
9
10
11
12

# 实现一个JSONP

function jsonp(url, jsonpCallback, success) {
  let script = document.createElement('script')
  script.src = url
  script.async = true
  script.type = 'text/javascript'
  window[jsonpCallback] = function(data) {
    success && success(data)
  }
  document.body.appendChild(script)
}
jsonp('http://xxx', 'callback', function(value) {
  console.log(value)
})
1
2
3
4
5
6
7
8
9
10
11
12
13

# 实现防抖函数(debounce)

防抖和节流的作用:

都是在高频事件中防止函数被多次调用,是一种性能优化的方案。

区别在于:

  • 防抖函数只会在高频事件结束后 n 毫秒调用一次函数
  • 节流函数会在高频事件触发过程当中每隔 n 毫秒调用一次函数。

什么是防抖:一个事件在触发后,设置定时器,若n秒内该事件没有再次发生,那么执行这个函数,如果n秒内该事件再次发生了,那么定时器重新开始计时。

应用

  • 每次 resize / scroll 触发统计事件
  • 文本输入的验证 (连续输入文字后发送AJAX请求进行验证,验证一次就好)
// 防抖函数,非立即执行
const debounce = (fn, delay) => {
  let timer = null;
  return (...args) => {
    if(timer) {
	  clearTimeout(timer);
	  timer = null
	}
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
//防抖(立即执行)
function debounce(fn, delay = 500) {
  let timer = null;
  let flag = true
  return function (...args) {
    if (timer) {
      clearTimeout(timer);
      timer = null
    }
    if (flag === true) {
      fn.apply(this, args);
      flag = false
    }
    timer = setTimeout(() => {
      flag = true
      timer = null
    }, delay)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * 防抖函数
 * 
 * - 立即执行
 * - 非立即执行
 * @param {*} fn 
 * @param {*} delay 
 * @param {*} isImmediate 
 */
function debounce(fn, delay = 500, isImmediate = true) {
  let timer = null;
  let flag = true
  if (isImmediate === true) {
    return function (...args) {
      if (timer) {
        clearTimeout(timer);
        timer = null
      }
      if (flag === true) {
        fn.apply(this, args);
        flag = false
      }
      timer = setTimeout(() => {
        flag = true
        timer = null
      }, delay)
    }
  } else {
    return function (...args) {
      if (timer) {
        clearTimeout(timer);
        timer = null
      }
      timer = setTimeout(() => {
        fn.apply(this, args);
        timer = null;
      }, delay)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 实现节流函数(throttle)

什么是节流:一个事件在n秒内被多次触发,但绑定的函数在期间只会执行一次(也就是降低函数的执行频率)

应用

  • 与mousemove keyup/keydown 等相密切联系的函数
// 节流函数(非立即执行)
const throttle = (fn, delay = 500) => {
  let flag = true;
  return (...args) => {
    if (flag) {
      flag = false;
      setTimeout(() => {
        fn.apply(this, args);
        flag = true;
      }, delay);
	}
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
//节流(立即执行)
function throttle_2(fn,delay = 500){
  var flag = true;
  var timer = null;
  return function(...args){
    if(flag) {
      fn.apply(this,args);
      flag = false;
      timer = setTimeout(() => {
        flag = true
      },delay)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//节流(合并)
function throttle_merge(fn,delay = 500,isImmediate = false){
  var flag = true;
  var timer = null;
  if(isImmediate){
    return function(...args){
      if(flag) {
        fn.apply(this,args);
        flag = false;
        timer = setTimeout(() => {
          flag = true
        },delay)
      }
    }
  } else {
	return function(...args){
      if(flag == true){
        flag = false
          timer = setTimeout(() => {
          fn.apply(this,args)
          flag = true
        },delay)
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 实现Event(event bus)

event bus既是node中各个模块的基石,又是前端组件通信的依赖手段之一,同时涉及了订阅-发布设计模式,是非常重要的基础

简单版:

class EventEmeitter {
  constructor() {
    this._events = this._events || new Map(); // 储存事件/回调键值对
    this._maxListeners = this._maxListeners || 10; // 设立监听上限
  }
}


// 触发名为type的事件
EventEmeitter.prototype.emit = function(type, ...args) {
  let handler;
  // 从储存事件键值对的this._events中获取对应事件回调函数
  handler = this._events.get(type);
  if (args.length > 0) {
    handler.apply(this, args);
  } else {
    handler.call(this);
  }
  return true;
};

// 监听名为type的事件
EventEmeitter.prototype.addListener = function(type, fn) {
  // 将type事件以及对应的fn函数放入this._events中储存
  if (!this._events.get(type)) {
    this._events.set(type, fn);
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

面试版:

class EventEmeitter {
  constructor() {
    this._events = this._events || new Map(); // 储存事件/回调键值对
    this._maxListeners = this._maxListeners || 10; // 设立监听上限
  }
}

// 触发名为type的事件
EventEmeitter.prototype.emit = function(type, ...args) {
  let handler;
  // 从储存事件键值对的this._events中获取对应事件回调函数
  handler = this._events.get(type);
  if (args.length > 0) {
    handler.apply(this, args);
  } else {
    handler.call(this);
  }
  return true;
};

// 监听名为type的事件
EventEmeitter.prototype.addListener = function(type, fn) {
  // 将type事件以及对应的fn函数放入this._events中储存
  if (!this._events.get(type)) {
    this._events.set(type, fn);
  }
};

// 触发名为type的事件
EventEmeitter.prototype.emit = function(type, ...args) {
  let handler;
  handler = this._events.get(type);
  if (Array.isArray(handler)) {
    // 如果是一个数组说明有多个监听者,需要依次此触发里面的函数
    for (let i = 0; i < handler.length; i++) {
      if (args.length > 0) {
        handler[i].apply(this, args);
      } else {
        handler[i].call(this);
      }
    }
  } else {
    // 单个函数的情况我们直接触发即可
    if (args.length > 0) {
      handler.apply(this, args);
    } else {
      handler.call(this);
    }
  }

  return true;
};

// 监听名为type的事件
EventEmeitter.prototype.addListener = function(type, fn) {
  const handler = this._events.get(type); // 获取对应事件名称的函数清单
  if (!handler) {
    this._events.set(type, fn);
  } else if (handler && typeof handler === "function") {
    // 如果handler是函数说明只有一个监听者
    this._events.set(type, [handler, fn]); // 多个监听者我们需要用数组储存
  } else {
    handler.push(fn); // 已经有多个监听者,那么直接往数组里push函数即可
  }
};

EventEmeitter.prototype.removeListener = function(type, fn) {
  const handler = this._events.get(type); // 获取对应事件名称的函数清单

  // 如果是函数,说明只被监听了一次
  if (handler && typeof handler === "function") {
    this._events.delete(type, fn);
  } else {
    let postion;
    // 如果handler是数组,说明被监听多次要找到对应的函数
    for (let i = 0; i < handler.length; i++) {
      if (handler[i] === fn) {
        postion = i;
      } else {
        postion = -1;
      }
    }
    // 如果找到匹配的函数,从数组中清除
    if (postion !== -1) {
      // 找到数组对应的位置,直接清除此回调
      handler.splice(postion, 1);
      // 如果清除后只有一个函数,那么取消数组,以函数形式保存
      if (handler.length === 1) {
        this._events.set(type, handler[0]);
      }
    } else {
      return this;
    }
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

# 实现简单的 EventEmiter,包含事件绑定,事件触发以及移除

class EventEmiter {
    constructor() {
        this.events = {}
    }
    emit(event, ...args) {
        this.events[event].forEach(fn => {
            fn.apply(this, args)
        })
    }
    on(event, fn) {
        if (this.events[event]) {
            this.events[event].push(fn)
        } else {
            this.events[event] = [fn]
        }
    }
    remove(event) {
        delete this.events[event]
    }
	once (event, fn) {
        this.on(event, (...args) => {
            fn(...args);
            this.off(event)
        })
    }
}

const eventHub = new EventEmiter()

eventHub.on('test', data => {
    console.log(data)
})

eventHub.emit('test', 1)
console.log(2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# settimeout实现interval(注意和普通的要无差别体验)

;(() => {
  const list = new Set();
  function myInterval(fn, ms) {
    const ref = {};
    const exec = () => {
      return setTimeout(() => {
        fn.apply(null);
        const timer = exec();
        ref.current = timer;
      }, ms);
    };
    ref.current = exec();
    list.add(ref);
    return ref;
  }

  function myClearInterval(ref) {
    clearTimeout(ref.current);
    list.delete(ref);
  }
  window.myInterval = myInterval;
  window.myClearInterval = myClearInterval;
})()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 一堆数字字符串组成最大数是多少[50, 2, 5, 9] => 95502 (字典序+贪心)

function getMaxNumber(arr) {
  return arr.sort().reduce((acc = '', cur) => Math.max(+`${acc}${cur}`, +`${cur}${acc}`));
}
1
2
3

# 深拷贝

简单版:

function deepClone(obj){
    //判断是否是简单数据类型,
    if(typeof obj == "object"){
        //复杂数据类型
        var result = obj.constructor == Array ? [] : {};
        for(let i in obj){
            result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];
        }
    }else {
        //简单数据类型 直接 == 赋值
        var result = obj;
    }
    return result;
}
let obj = {
  a: [1, 2, 3],
  b: {
    c: 2,
    d: 3
  }
}
let newObj = deepClone(obj)
newObj.b.c = 1
console.log(obj.b.c) // 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

面试版:

实现一个 deepClone 函数:

  • 如果是基本数据类型,直接返回
  • 如果是 RegExp 或者 Date 类型,返回对应类型
  • 如果是复杂数据类型,递归。
  • 考虑循环引用的问题
// 递归拷贝
function deepClone(obj, hash = new WeakMap()) {
  if (obj instanceof RegExp) return new RegExp(obj)
  if (obj instanceof Date) return new Date(obj)
  if (obj === null || typeof obj !== 'object') {
	// 如果不是复杂数据类型,直接返回	
    return obj
  }
  if(hash.has(obj)) {
	// 如果已经处理过相同的对象,直接获取(解决循环引用)
	return hash.get(obj)
  }
  /**
   * 如果 obj 是数组,那么 obj.constructor 是 [Function: Array]
   * 如果 obj 是对象,那么 obj.constructor 是 [Function: Object]
   */
  let t = new obj.constructor()
  hash.set(obj, t)
  for (let ikey in obj) {
	// 递归
	if (obj.hasOwnProperty(key)) { // 是否是自身的属性
	  t[key] = deepClone(obj[key], hash)
	}  
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

深入版:

/**
 * deep clone
 * @param  {[type]} parent object 需要进行克隆的对象
 * @return {[type]}        深克隆后的对象
 */
const clone = parent => {
  // 判断类型
  const isType = (obj, type) => {
    if (typeof obj !== "object") return false;
    const typeString = Object.prototype.toString.call(obj);
    let flag;
    switch (type) {
      case "Array":
        flag = typeString === "[object Array]";
        break;
      case "Date":
        flag = typeString === "[object Date]";
        break;
      case "RegExp":
        flag = typeString === "[object RegExp]";
        break;
      default:
        flag = false;
    }
    return flag;
  };

  // 处理正则
  const getRegExp = re => {
    var flags = "";
    if (re.global) flags += "g";
    if (re.ignoreCase) flags += "i";
    if (re.multiline) flags += "m";
    return flags;
  };
  // 维护两个储存循环引用的数组
  const parents = [];
  const children = [];

  const _clone = parent => {
    if (parent === null) return null;
    if (typeof parent !== "object") return parent;

    let child, proto;

    if (isType(parent, "Array")) {
      // 对数组做特殊处理
      child = [];
    } else if (isType(parent, "RegExp")) {
      // 对正则对象做特殊处理
      child = new RegExp(parent.source, getRegExp(parent));
      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
    } else if (isType(parent, "Date")) {
      // 对Date对象做特殊处理
      child = new Date(parent.getTime());
    } else {
      // 处理对象原型
      proto = Object.getPrototypeOf(parent);
      // 利用Object.create切断原型链
      child = Object.create(proto);
    }

    // 处理循环引用
    const index = parents.indexOf(parent);

    if (index != -1) {
      // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象
      return children[index];
    }
    parents.push(parent);
    children.push(child);

    for (let i in parent) {
      // 递归
      child[i] = _clone(parent[i]);
    }

    return child;
  };
  return _clone(parent);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

# 深比较

function deepCompare(a, b){
  if(a === null
    || typeof a !== 'object'
    || b === null
    || typeof b !== 'object'){
    return a === b
  }

  const propsA = Object.getOwnPropertyDescriptors(a)
  const propsB = Object.getOwnPropertyDescriptors(b)
  if(Object.keys(propsA).length !== Object.keys(propsB).length){
    return false
  }

  return Object.keys(propsA).every( key => deepCompare(a[key], b[key]))

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 手写 new

function create () {
  // 1. 获取构造函数,并且删除 arguments 中的第一项
  var Con = [].shift.call(arguments);
  // 2. 创建一个空的对象并链接到构造函数的原型,使它能访问原型中的属性
  var obj = Object.create(Con.prototype);
  // 3. 使用apply改变构造函数中this的指向实现继承,使obj能访问到构造函数中的属性
  var ret = Con.apply(obj, arguments);
  // 4. 优先返回构造函数返回的对象
  return ret instanceof Object ? ret : obj;
}
1
2
3
4
5
6
7
8
9
10
function Dog(name){
    this.name = name
}
Dog.prototype.sayName = function(){
    console.log(this.name)
}
// 上面是本身Dog
function _new(fn,...args){   // ...args为ES6展开符,也可以使用arguments
    //先用Object创建一个空的对象,
    const obj = Object.create(fn.prototype)  //fn.prototype代表 用当前对象的原型去创建
    //现在obj就代表Dog了,但是参数和this指向没有修改
    const rel = fn.apply(obj,args)
    //正常规定,如何fn返回的是null或undefined(也就是不返回内容),我们返回的是obj,否则返回rel
    return rel instanceof Object ? rel : obj
}
var _newDog = _new(Dog,'这是用_new出来的小狗')
_newDog.sayName()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# call

ES3:

function fnFactory(context) {
  var unique_fn = "fn";
  while (context.hasOwnProperty(unique_fn)) {
    unique_fn = "fn" + Math.random();
  }
  return unique_fn;
}
Function.prototype.call2 = function(context) {
  // 1. 若是传入的context是null或者undefined时指向window;
  // 2. 若是传入的是原始数据类型, 原生的call会调用 Object() 转换
  context = (context !== null && context !== undefined) ? Object(context) : window;
  // 3. 创建一个独一无二的fn函数的命名
  var fn = fnFactory(context);
  // 4. 这里的this就是指调用call的那个函数
  // 5. 将调用的这个函数赋值到context中, 这样之后执行context.fn的时候, fn里的this就是指向context了
  context[fn] = this;
  // 6. 定义一个数组用于放arguments的每一项的字符串: ['agruments[1]', 'arguments[2]']
  var args = [];
  // 7. 要从第1项开始, 第0项是context
  for (var i = 1, l = arguments.length; i < l; i++) {
    args.push("arguments[" + i + "]");
  }
  // 8. 使用eval()来执行fn并将args一个个传递进去
  var result = eval("context[fn](" + args + ")");
  // 9. 给context额外附件了一个属性fn, 所以用完之后需要删除
  delete context[fn];
  // 10. 函数fn可能会有返回值, 需要将其返回
  return result;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

ES6:

Function.prototype.myCall = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  context = context || window
  context.fn = this
  const args = [...arguments].slice(1)
  const result = context.fn(...args)
  delete context.fn
  return result
}
1
2
3
4
5
6
7
8
9
10
11

# apply

ES3:

function fnFactory(context) {
  var unique_fn = "fn";
  while (context.hasOwnProperty(unique_fn)) {
    unique_fn = "fn" + Math.random();
  }
  return unique_fn;
}
Function.prototype.apply2 = function(context, arr) {
  // 1. 若是传入的context是null或者undefined时指向window;
  // 2. 若是传入的是原始数据类型, 原生的call会调用 Object() 转换
  context = context ? Object(context) : window;
  // 3. 创建一个独一无二的fn函数的命名
  var fn = fnFactory(context);
  // 4. 这里的this就是指调用call的那个函数
  // 5. 将调用的这个函数赋值到context中, 这样之后执行context.fn的时候, fn里的this就是指向context了
  context[fn] = this;

  var result;
  // 6. 判断有没有第二个参数
  if (!arr) {
    result = context[fn]();
  } else {
    // 7. 有的话则用args放每一项的字符串: ['arr[0]', 'arr[1]']
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]");
    }
    // 8. 使用eval()来执行fn并将args一个个传递进去
    result = eval("context[fn](" + args + ")");
  }
  // 9. 给context额外附件了一个属性fn, 所以用完之后需要删除
  delete context[fn];
  // 10. 函数fn可能会有返回值, 需要将其返回
  return result;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

ES6:

Function.prototype.myApply = function(context) {
  if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  context = context || window
  context.fn = this
  let result
  // 处理参数和 call 有区别
  if (arguments[1]) {
    result = context.fn(...arguments[1])
  } else {
    result = context.fn()
  }
  delete context.fn
  return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# bind

函数内的this表示的就是调用的函数 可以将上下文传递进去, 并修改this的指向 返回一个函数 可以传入参数 柯里化 一个绑定的函数也能使用new操作法创建对象, 且提供的this会被忽略

Function.prototype.bind2 = function(context) {
 if (typeof this !== 'function') {
    throw new TypeError('Error')
  }
  const _this = this
  const args = [...arguments].slice(1)
  // 返回一个函数
  return function F() {
    // 因为返回了一个函数,我们可以 new F(),所以需要判断
    if (this instanceof F) {
      return new _this(...args, ...arguments)
    }
    return _this.apply(context, args.concat(...arguments))
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 实现instanceOf

function myInstanceof(left, right) {
  let prototype = right.prototype
  left = left.__proto__
  while (true) {
    if (left === null || left === undefined)
      return false
    if (prototype === left)
      return true
    left = left.__proto__
  }
}
1
2
3
4
5
6
7
8
9
10
11

# 模拟Object.create

// Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
function create(proto) {
  function F() {}
  F.prototype = proto;

  return new F();
}
1
2
3
4
5
6
7

# 实现简单的 promise

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function MyPromise(fn) {
  const that = this
  that.state = PENDING
  that.value = null
  that.resolvedCallbacks = []
  that.rejectedCallbacks = []
  
  function resolve(value) {
    if (that.state === PENDING) {
      that.state = RESOLVED
      that.value = value
      that.resolvedCallbacks.map(cb => cb(that.value))
    }
  }

  function reject(value) {
    if (that.state === PENDING) {
      that.state = REJECTED
      that.value = value
      that.rejectedCallbacks.map(cb => cb(that.value))
    }
  }

  try {
    fn(resolve, reject)
  } catch (e) {
    reject(e)
  }
}

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  const that = this
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
  onRejected =
    typeof onRejected === 'function'
      ? onRejected
      : r => {
          throw r
        }
  if (that.state === PENDING) {
    that.resolvedCallbacks.push(onFulfilled)
    that.rejectedCallbacks.push(onRejected)
  }
  if (that.state === RESOLVED) {
    onFulfilled(that.value)
  }
  if (that.state === REJECTED) {
    onRejected(that.value)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

复合 Promise + 规范:

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function Promise(excutor) {
    let that = this; // 缓存当前promise实例对象
    that.status = PENDING; // 初始状态
    that.value = undefined; // fulfilled状态时 返回的信息
    that.reason = undefined; // rejected状态时 拒绝的原因
    that.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数
    that.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数

    function resolve(value) { // value成功态时接收的终值
        if(value instanceof Promise) {
            return value.then(resolve, reject);
        }
        // 实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
        setTimeout(() => {
            // 调用resolve 回调对应onFulfilled函数
            if (that.status === PENDING) {
                // 只能由pending状态 => fulfilled状态 (避免调用多次resolve reject)
                that.status = FULFILLED;
                that.value = value;
                that.onFulfilledCallbacks.forEach(cb => cb(that.value));
            }
        });
    }
    function reject(reason) { // reason失败态时接收的拒因
        setTimeout(() => {
            // 调用reject 回调对应onRejected函数
            if (that.status === PENDING) {
                // 只能由pending状态 => rejected状态 (避免调用多次resolve reject)
                that.status = REJECTED;
                that.reason = reason;
                that.onRejectedCallbacks.forEach(cb => cb(that.reason));
            }
        });
    }

    // 捕获在excutor执行器中抛出的异常
    // new Promise((resolve, reject) => {
    //     throw new Error('error in excutor')
    // })
    try {
        excutor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

Promise.prototype.then = function(onFulfilled, onRejected) {
    const that = this;
    let newPromise;
    // 处理参数默认值 保证参数后续能够继续执行
    onFulfilled =
        typeof onFulfilled === "function" ? onFulfilled : value => value;
    onRejected =
        typeof onRejected === "function" ? onRejected : reason => {
            throw reason;
        };
    if (that.status === FULFILLED) { // 成功态
        return newPromise = new Promise((resolve, reject) => {
            setTimeout(() => {
                try{
                    let x = onFulfilled(that.value);
                    resolvePromise(newPromise, x, resolve, reject); // 新的promise resolve 上一个onFulfilled的返回值
                } catch(e) {
                    reject(e); // 捕获前面onFulfilled中抛出的异常 then(onFulfilled, onRejected);
                }
            });
        })
    }

    if (that.status === REJECTED) { // 失败态
        return newPromise = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(that.reason);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }

    if (that.status === PENDING) { // 等待态
        // 当异步调用resolve/rejected时 将onFulfilled/onRejected收集暂存到集合中
        return newPromise = new Promise((resolve, reject) => {
            that.onFulfilledCallbacks.push((value) => {
                try {
                    let x = onFulfilled(value);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
            that.onRejectedCallbacks.push((reason) => {
                try {
                    let x = onRejected(reason);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

# 手动实现一下相关 API

# 实现maxRequest,成功后resolve结果,失败后重试,尝试超过一定次数才真正的reject

function maxRequest(fn, maxNum) {
    return new Promise((resolve, reject) => {
        if (maxNum === 0) {
            reject('max request number')
            return
        }
        Promise.resolve(fn()).then(value => {
            resolve(value)
        }).catch(() => {
            return maxRequest(fn, maxNum - 1)
        })
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 对象扁平化【编程】

function flattenObj(data) {
    var result = {};
    function recurse(cur, prop) {
        if (Object(cur) !== cur) {
            result[prop] = cur;
        } else if (Array.isArray(cur)) {
            for (var i = 0, l = cur.length; i < l; i++)
                recurse(cur[i], prop + "[" + i + "]");
            if (l == 0)
                result[prop] = [];
        } else {
            var isEmpty = true;
            for (var p in cur) {
                isEmpty = false;
                recurse(cur[p], prop ? prop + "." + p : p);
            }
            if (isEmpty && prop)
                result[prop] = {};
        }
    }
    recurse(data, "");
    return result;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 实现一个LazyMan,可以按照以下方式调用:

LazyMan(“Hank”)输出:
Hi! This is Hank!
 
LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~
 
LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
Hi This is Hank!
Eat dinner~
Eat supper~
 
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class _LazyMan {
  constructor(name) {
    this.tasks = [];
    const task = () => {
      console.log(`Hi! This is ${name}`);
      this.next();
    }
    this.tasks.push(task);
    setTimeout(() => {               // 把 this.next() 放到调用栈清空之后执行
      this.next();
    }, 0);
  }
 
  next() {
    const task = this.tasks.shift(); // 取第一个任务执行
    task && task();
  }
 
  sleep(time) {
    this._sleepWrapper(time, false);
    return this;                     // 链式调用
  }
 
  sleepFirst(time) {
    this._sleepWrapper(time, true);
    return this;
  }
 
  _sleepWrapper(time, first) {
    const task = () => {
      setTimeout(() => {
        console.log(`Wake up after ${time}`);
        this.next();
      }, time * 1000)
    }
    if (first) {
      this.tasks.unshift(task);     // 放到任务队列顶部
    } else {
      this.tasks.push(task);        // 放到任务队列尾部
    }
  }
 
  eat(name) {
    const task = () => {
      console.log(`Eat ${name}`);
      this.next();
    }
    this.tasks.push(task);
    return this;
  }
}
 
function LazyMan(name) {
  return new _LazyMan(name);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55